/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.servlets;

import com.codemagi.database.*;
import com.codemagi.util.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.log4j.*;

/**   
 * Lists is a cache for two-factor queries used to populate &lt;cminput:select&gt; lists (and others)
 * 
 * Lists class extends servlet so it can access the application context  
 * and be loaded at startup, nothing more. It does not define service, doGet or doPost methods. 
 * 
 * Usage: 
 * 
 * 1. Add a servlet-config for com.codemagi.servlets.Lists to your web.xml. 
 *    Be sure to define the init-param: jndi.database 
 * 
 * 2. In your JSP, add &lt;jsp:useBean name="lists" scope="application" class="com.codemagi.servlets.Lists" /&gt;
 *
 * 3. Get lists from the cache with lists.get("SELECT somthing FROM something")
 *
 * @version 1.0
 * @author August Detlefsen for CodeMagi, Inc.
 */
public final class Lists extends HttpServlet {

    //set to true to view debug output         
    Logger log = Logger.getLogger(this.getClass());

    //DB CONNECT INFO  
    protected String jndiConnect = "";

    //MEMBERS
    protected ReferenceMap queries = new ReferenceMap();

    protected long cacheTimeout = -1;
    protected ReferenceMap queryTimes = new ReferenceMap();


    /**                                                    
     * Initializes the servlet                                            
     */
    public void init(ServletConfig config) throws ServletException {
        log.debug("com.codemagi.servlets.Lists: Initializing");

        //initialize superclass                                               
        super.init(config);

	jndiConnect = config.getInitParameter("jndi.database");
	log.debug("JNDI Connect: " + jndiConnect);
	if (Utils.isEmpty(jndiConnect)) 
	    throw new ServletException("Unable to initialize Lists servlet -No JNDI connect info specified");
	
	String sCacheTimeout = config.getInitParameter("cacheTimeout");
	if ( !Utils.isEmpty(sCacheTimeout) ) {
	    try {
		cacheTimeout = Long.decode(sCacheTimeout);
		log.debug("Cache Timeout set to: " + cacheTimeout + " msec");
	    } catch (NumberFormatException nfe) {
		log.warn("WARNING: Invalid number format supplied for cacheTimeout parameter");
	    }
	}

	//put a reference to this servlet in the application context  
        getServletContext().setAttribute(config.getServletName(), this);
    }

    
    /**                                                                                                   
     * Generates the static (non-DB) lists and manages the internal list of allowed queries. 
     * Extending classes should override this method if non-DB lists are used.
     */
    protected void generateLists() {
	queries    = new ReferenceMap();
	queryTimes = new ReferenceMap();
    }


    /**                                                              
     * Generates the DataBean to be used for queries. Depends on the application 
     * init parameter "database" for JNDI connect String.
     */
    protected TableDataSourceBean generateDataBean() {
	log.debug("com.codemagi.servlets.Lists: Generating TableDataSourceBean using: " + jndiConnect);
	return new TableDataSourceBean( jndiConnect );
    }
    
    
    /**                                                              
     * Generates an OrderedTable from the input query                                       
     *                                                               
     * @param query  DB query used to generate the OrderedTable                                  
     */
    private OrderedTable generateTable(String query) {
	return generateTable(query, null);
    }

    
    /**                                                              
     * Generates an OrderedTable from the input query, setting the default element                         
     *                                                               
     * @param query     DB query used to generate the OrderedTable                              
     * @param defaultElement Text to be used for the default element                                
     */
    private OrderedTable generateTable(String query, String defaultElement) {
	
	TableDataSourceBean dataBean = generateDataBean();

	dataBean.setQuery(query);
	dataBean.executeQuery();
	
	OrderedTable output = dataBean.getQueryResult();
	
	if (!Utils.isEmpty(defaultElement)) {
	    output = (OrderedTable)output.clone();
	    output.putAt("", defaultElement, 0);
	}

	queries.put(query, output);
	queryTimes.put(query, new Date().getTime());
	
	return output;
    }
    
    
    /**                                                              
     * Updates all lists in this bean                                               
     */
    public void update() {
	generateLists();
    }
    
    
    /**                                                              
     * Updates a single list in this bean                                             
     */
    public void update(String query) {
	queries.remove(query);
	queryTimes.remove(query);
    }

  
    /**                                                              
     * Overridden version of get(String, String) that does not require a default element.                     
     */
    public OrderedTable get(String query) {
	return get(query, null);
    }
    
    
    /**                                                              
     * Returns an OrderedTable based on the query selected. Loads the table from the DB if it has not yet been loaded.       
     *                                                               
     * @param query  One of the existing queries defined as fields of this class                         
     * @param defaultElement  Default element to add at the start of the list                          
     */
    public OrderedTable get(String query, String defaultElement) {
	
	//get table from hash                                                   
	OrderedTable list = (OrderedTable)queries.get(query);

	//if a cache timeout has been set
	if (cacheTimeout > -1) {
	    //check if the cache has expired
	    Long lastUpdate  = (Long)queryTimes.get(query);
	    log.debug("Lists.get(): cache lastUpdate: " + lastUpdate + " current: " + System.currentTimeMillis() );
	    if (lastUpdate == null || (lastUpdate.longValue() + cacheTimeout < System.currentTimeMillis())) {
		log.debug("Lists.get(): cache expired, updating table");
		list = generateTable(query);
	    }
	} 

	//if the list was not already in the cache, generate it now
	if (list == null) {
	    log.debug("Lists.get(): query not found, updating table");
	    list = generateTable(query);
	}
	
	//if a default element is present, insert it into a cloned version of the list
	//it must be cloned or the cached list will be affected as well
	if (!Utils.isEmpty(defaultElement)) {
	    OrderedTable output = (OrderedTable)list.clone();
	    output.putAt("", defaultElement, 0);
	    return output;
	}
	
	return list;
    }
    
    
    /**                                                              
     * Returns servlet info as <code>String</code>.                                        
     *                                                               
     * @return String  Info about this servlet                                          
     */
    public String getServletInfo() {
	return "Cache for DB-generated lists";
    }

    
    /**                                                              
     * Destroys this servlet                                                    
     */
    public void destroy() {
	log.debug("Lists: terminating");
	
	queries.clear();
	queries = null;

	queryTimes.clear();
	queryTimes = null;

	super.destroy();
    }
    
}
